# -*- coding: utf-8 -*- 
#! /usr/bin/python

import asynchat, asyncore, socket, SimpleHTTPServer, select, urllib
import posixpath, sys, cgi, cStringIO, os, traceback, shutil

import asynchat
import asyncore
import socket
from pprint import pprint
import SocketServer
from SimpleXMLRPCServer import SimpleXMLRPCServer
from xmlrpclib import Binary 
from ZODB import FileStorage, DB
from BTrees.OOBTree import OOBTree
import transaction
import re
import zlib
import pickle
#ZODB_FILE = '/tmp/test-filestorage.fs'
ZODB_FILE = 'test-filestorage.zodb'

COMMIT_SIM, COMMIT_TEXT = 0, 1
UPDATE_SIM, UPDATE_TEXT = 2, 3

class SpamFunctionality:
    """ This class is intended to be invoked remotely
        via XML-RPC. These methods are not for running local!
    """
    pickled = None
    
    def __init__(self):
        self.storage = FileStorage.FileStorage(ZODB_FILE)
        self.db = DB(self.storage)
        self.conn = self.db.open()
        self.dbroot = self.conn.root()

        # Ensure that a 'filterdb' key is present 
        # in the root
        if not self.dbroot.has_key('simdb'):
            self.dbroot['simdb'] = OOBTree()
            print 'Creating simdb'

        if not self.dbroot.has_key('textdb'):
            self.dbroot['textdb'] = OOBTree()
            print 'Creating textdb'

#===============================================================================
#    def __pickle_all(self):
#        """ Pickle all entries from database
#        """
#        simdb = self.dbroot['simdb']
#        textdb = self.dbroot['textdb']
#        sim_list = [entry.value for entry in simdb.values()]
#        text_list = [entry.value for entry in textdb.values()]
#        print sim_list, text_list
#        self.pickled = pickle.dumps({'sim_list':sim_list, 'text_list':text_list})
# 
#        #transaction.commit()
#        return True
#===============================================================================
     
    def add_sim(self, value_list):
        """ Add sim number regex to global database.
            Database is automatically repickled and rezipped
            so other users can easily download new data
            @value_list - list of regex
        """
        simdb = self.dbroot['simdb']
        for value in value_list:
            simdb[value] = value
        #filterdb._p_changed = True
        transaction.commit()
        
        #self.__pickle_all()
        return True
        
    def add_text(self, value_list):
        """ Add bodytext regex to global database.
            Database is automatically repickled and rezipped
            so other users can easily download new data
            @value_list - list of regex
        """
        textdb = self.dbroot['textdb']
        for value in value_list:
            textdb[value] = value
        #filterdb._p_changed = True
        transaction.commit()
        
        #self.__pickle_all()
        return True
    
    def get_sim_list(self):
        """ Return whole latest simlist
            this method is invoked when user requests
            an update
        """
        return [x for x in self.dbroot['simdb'].keys()]

    def get_text_list(self):
        """ Return whole latest textlist
            this method is invoked when user requests
            an update
        """
        return [x for x in self.dbroot['textdb'].keys()]            

    def close(self):
        """ Close connection with database
        """
        self.conn.close()
        self.db.close()
        self.storage.close()
        return True

    def __show_all(self):
        """ Print all avaliable sims and texts stored in database
        """
        pprint([x for x in self.dbroot['simdb'].keys()])
        pprint([x for x in self.dbroot['textdb'].keys()])
        return True


class RequestHandler(asynchat.async_chat):


    def __init__(self, conn, host, port, server):
        asynchat.async_chat.__init__(self,conn)
        #self.client_address = addr
        self.host, self.port = host, port
        self.server = server
        self.type = server.type
        self.logger = server.logger
        # set the terminator : when it is received, this means that the
        # http request is complete ; control will be passed to
        # self.found_terminator
        self.set_terminator('\x00')
        self.rfile = cStringIO.StringIO()
        self.found_terminator = self.handle_request_line
        self.wfile = cStringIO.StringIO()
        #print 'Handler created'
        self.sim_list = []
        self.text_list = []

    def writable(self):
        return True
    
    def handle_write(self):
        self.logger.info('handle write')
        print 'handle write'
        if self.type == UPDATE_SIM:
            print 'request for update sim'
            self.logger.info('request for update sim')
            data_list = ['sim_ala','sim_ma','sim_kota']
            sent_size = [self.send('%s\x00' % (data)) for data in data_list]
            self.logger.info('data sizes sent')
            self.logger.info(sent_size)
            self.close()
            
        elif self.type == UPDATE_TEXT:
            print 'request for update text'
            self.logger.info('request for update text')
            data_list = ['text_kot','text_ma','text_alicje']
            sent_size = [self.send('%s\x00' % (data)) for data in data_list]
            self.logger.info('data sizes sent')
            self.logger.info(sent_size)
            self.close()
        
        else:
            print self.type
            self.logger.warning('Sth strange happened')
            print 'Sth strange happened'

    def collect_incoming_data(self, data):
        """Collect the data arriving on the connection"""
        self.rfile.write(data)
        #print 'new incoming data'

    def handle_request_line(self):
        """Called when the http request line and headers have been received"""
        # prepare attributes needed in parse_request()
        #print 'handling line'
        self.rfile.seek(0)
        self.raw_requestline = self.rfile.readline()
        self.rfile = cStringIO.StringIO()
        #print self.raw_requestline

        if self.type == COMMIT_SIM:
            self.sim_list.append(self.raw_requestline)
            self.logger.info('received sim_list')
            self.logger.info(self.sim_list)
            print 'received sim_list', self.sim_list
        elif self.type == COMMIT_TEXT:
            self.text_list.append(self.raw_requestline)
            self.logger.info('received text_list')
            self.logger.info(self.text_list)
            print 'received text_list', self.text_list
        else:
            self.logger.warning('Sth strange happened')
            print 'sth else read'

    def handle_error(self):
        traceback.print_exc(sys.stderr)
        self.close()
        
#===============================================================================
#    def handle_expt(self):
#        self.logger.error('connection failed, shutdown')
#        self.close() # connection failed, shutdown
#===============================================================================
        
    def handle_close(self):
        self.close()
        spam_machine = SpamFunctionality()
        if self.type == COMMIT_SIM:
            spam_machine.add_sim(self.sim_list)
            self.logger.info('adding bunch of sims')
            self.logger.info(self.sim_list)
            print 'adding bunch of sims'
            print self.sim_list
        elif self.type == COMMIT_TEXT:
            spam_machine.add_text(self.text_list)
            self.logger.info('adding bunch of texts')
            self.logger.info(self.text_list)
            print 'adding bunch of texts'
            print self.text_list
            
        spam_machine.close()
        self.logger.info('connection remotely closed, data saved')
        print 'connection remotely closed, data saved' 
        
        

class Server(asyncore.dispatcher):
    """Copied from http_server in medusa"""
    def __init__ (self, ip, port, handler, type, logger):
        self.ip = ip
        self.port = port
        self.handler = handler
        self.type = type
        self.logger = logger
        asyncore.dispatcher.__init__ (self)
        self.create_socket (socket.AF_INET, socket.SOCK_STREAM)

        self.set_reuse_addr()
        self.bind ((ip, port))

        # lower this to 5 if your OS complains
        self.listen (1024)

    def handle_accept (self):
        try:
            conn, (host, port) = self.accept()
            self.logger.info('Connection accepted from %s %s' % (host, port))
            print 'Connection accepted'
        except socket.error:
            self.logger.warning('server accept() threw an exception')
            return
        except TypeError:
            self.logger.warning('server accept() threw EWOULDBLOCK')
            return
        # creates an instance of the handler class to handle the request/response
        # on the incoming connexion
        self.handler(conn, host, port, self)


class proxy_server(asyncore.dispatcher):
    
    def __init__(self, host, port, type):
        """ Init dispacher object
        """ 
        asyncore.dispatcher.__init__(self)
        
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.set_reuse_addr()
        self.type = type
        here = (host, port)
        self.bind(here)
        self.listen(5)

    def writable(self):
        print 'it is writable'
        return 1

    #def handle_expt(self):
    #    self.close() # connection failed, shutdown


    def handle_read(self):
        print 'handle read'
        if self.type == COMMIT_SIM:
            print 'request for commit sim'
            download_data = ''
            while True:
                part = self.recv(1024)
                if not part:
                    break
                download_data += part
            sim_list = download_data.split('\x00')
            print 'received sim_list', sim_list

        elif self.type == COMMIT_TEXT:
            print 'request for commit text'
            download_data = ''
            while True:
                part = self.recv(1024)
                if not part:
                    break
                download_data += part
            text_list = download_data.split('\x00')
            print 'received sim_list', text_list
        else:
            print 'sht else read'

    def handle_write(self):
        print 'handle write'
        if self.type == UPDATE_SIM:
            print 'request for update sim'
            data_list = ['sim_ala','sim_ma','sim_kota']
            sent_size = [self.send('%s\x00' % (data)) for data in data_list]
            self.close()
            
        elif self.type == UPDATE_TEXT:
            print 'request for update text'
            data_list = ['text_kot','text_ma','text_alicje']
            sent_size = [self.send('%s\x00' % (data)) for data in data_list]
            self.send('ala')
            self.close()
        
        else:
            print 'Sth strange happened'

    def handle_accept(self):
        """ Invoked automatically when new incomming connection
            Its purpose is to accept connection, and process
            properly, according to mode (listening port)
        """
        socket, host = self.accept()
        print 'connection from %s for %s' % (host, self.type)
                 
    def handle_close(self):
        print 'Closing channel'
        self.close()


if __name__ == '__main__':
    import sys
    import string
    if len(sys.argv) < 6:
        print 'Usage: %s <server-host> <server-sim-port> <server-text-port>' % sys.argv[0]
        addr = '127.0.0.1'
        commit_sim_port = '8991'
        commit_text_port = '8992'
        update_sim_port = '8993'
        update_text_port = '8994'

        print 'Server running at %s' % (addr)
        print 'commit sim port:%s commit text port:%s' % (commit_sim_port, commit_text_port)
        print 'update sim port:%s update text port:%s' % (update_sim_port, update_text_port)        
        proxy_server(addr, string.atoi(commit_sim_port), COMMIT_SIM)
        proxy_server(addr, string.atoi(commit_text_port), COMMIT_TEXT)
        proxy_server(addr, string.atoi(update_sim_port), UPDATE_SIM)
        proxy_server(addr, string.atoi(update_text_port), UPDATE_TEXT)

    else:
        import logging
        logger = logging.getLogger()
        hdlr = logging.FileHandler('log.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)
        logger.setLevel(logging.INFO)

        print 'running with default options'
        Server(sys.argv[1], string.atoi(sys.argv[2]), RequestHandler, COMMIT_SIM, logger)
        Server(sys.argv[1], string.atoi(sys.argv[3]), RequestHandler, COMMIT_TEXT, logger)
        Server(sys.argv[1], string.atoi(sys.argv[4]), RequestHandler, UPDATE_SIM, logger)
        Server(sys.argv[1], string.atoi(sys.argv[5]), RequestHandler, UPDATE_TEXT, logger)
        print 'Server running at %s' % (sys.argv[1])
        print 'commit sim port:%s commit text port:%s' % (sys.argv[2], sys.argv[3])
        print 'update sim port:%s update text port:%s' % (sys.argv[4], sys.argv[5])
        try:
            asyncore.loop(timeout=604800)
        except KeyboardInterrupt:
            print "Crtl+C pressed. Shutting down."
        

    #asyncore.loop(timeout=604800) #604800 equals one week
    

